<template>
  <div class="user-container">
    <!-- 面包屑导航 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>用户管理</el-breadcrumb-item>
      <el-breadcrumb-item>用户列表</el-breadcrumb-item>
    </el-breadcrumb>

    <!-- 卡片区域 -->
    <el-card class="box-card">
      <!-- 栅格排列的搜索区域 -->
      <el-row :gutter="20">
        <el-col :span="8">
          <!-- 将输入框的内容双向绑定到queryInfo中的query属性 -->
          <!-- 为输入框添加clearable属性实现清空输入框功能，
          同时绑定clear事件，清空时重新请求后端用户列表数据。
          由于clear事件触发时输入框中的已经数据被清空，同时双向绑定的query数据也为空，
          此时调用api会返回所有用户数据而不进行过滤 -->
          <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="initUserList">
            <!-- 单击时调用初始化用户列表函数，
              后端api根据读取到的query属性值，执行select-where语句，
              将查询结果通过initUserList中的语句对userList数组和total变量进行赋值，
              将新的用户列表渲染到页面中 -->
            <el-button slot="append" icon="el-icon-search" @click="initUserList"></el-button>
          </el-input>
        </el-col>
        <el-col :span="4">
          <el-button type="info" @click="addUserDialog = true">添加用户</el-button>
        </el-col>
      </el-row>

      <!-- 添加用户窗口区域 -->
      <!-- visible.sync属性需要一个布尔值，用于决定窗口是否显示，
        内部两个按钮单击事件会将visible.sync单向绑定的变量addUerDialog值设为false来隐藏窗口，
        本窗口需要一个契机来显示，即需要给上面栅格搜索区域的添加用户按钮绑定一个事件将addUserDialog设为真。
        本窗口的取消按钮用来关闭窗口并清空表单，绑定一个函数来实现这个功能 -->
      <el-dialog
        title="添加用户"
        :visible.sync="addUserDialog"
        width="50%"
        @close="addUserDialogClosed">

        <!-- 有验证的表单区域 -->
        <el-form :model="addUserForm" :rules="addUserRules" ref="addUserRef" label-width="70px">
          <el-form-item label="用户名" prop="username">
            <el-input v-model="addUserForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input v-model="addUserForm.password" type="password"></el-input>
          </el-form-item>
          <el-form-item label="邮箱" prop="email">
            <el-input v-model="addUserForm.email"></el-input>
          </el-form-item>
          <el-form-item label="手机号" prop="mobile">
            <el-input v-model="addUserForm.mobile"></el-input>
          </el-form-item>
        </el-form>

        <!-- 添加用户窗口的关闭区域 -->
        <span slot="footer" class="dialog-footer">
          <el-button @click="addUserDialog = false">取 消</el-button>
          <el-button type="primary" @click="addUser">确 定</el-button>
        </span>
      </el-dialog>

      <!-- 表格区域 -->
      <!-- table盒子的data属性用来绑定表格的数据源。
        stripe可以给表格设置隔行变色，需要一个boolean值
        border可以给表格设置边框，需要一个boolean -->
      <el-table :data="userList" stripe border>
        <el-table-column type="index" label="索引" width="60"></el-table-column>
        <!-- table-column的prop属性是该列的数据源，
          它的值是table的data属性绑定的数组中的一个对象。
          label为该列的名称，width为该列设置宽度 -->
        <el-table-column prop="username" label="用户名"></el-table-column>
        <el-table-column prop="email" label="邮箱"></el-table-column>
        <el-table-column prop="mobile" label="电话号码"></el-table-column>
        <el-table-column prop="role_name" label="权限类型"></el-table-column>
        <!-- 状态列使用switch组件将接口返回值的状态类型转换为switch开关 -->
        <el-table-column prop="mg_state" label="状态">
          <!-- 作用域插槽，接收一个scope变量 -->
          <template slot-scope="scope">
            {{scope.row.mg_state}}
            <!-- 开关组件触发change事件后调用changeUserState函数，传入作用域插槽中的scope.row -->
            <el-switch v-model="scope.row.mg_state" @change="changeUserState(scope.row)"></el-switch>
          </template>
        </el-table-column>
        <!-- 在操作列中放入三个按钮，实现查看、修改、删除功能，
          这些功能都需要用户id，所以使用作用域插槽。
          设置操作列的width属性指定宽度防止窗口缩小导致按钮换行 -->
        <el-table-column label="操作" width="130px">
          <template slot-scope="scope">
              <el-button type="primary" icon="el-icon-edit" circle size="mini" @click="showEditUserDialog(scope.row.id)"></el-button>
              <!-- 为齿轮按钮添加提示气泡 -->
              <!-- tooltip组件中，enterable作用是鼠标是否可以进入气泡，
                true：可以进入气泡，悬停时气泡不会消失
                false：穿过气泡，鼠标途径气泡区域时气泡照常消失 -->
              <el-tooltip class="item"
                effect="dark"
                content="权限修改"
                placement="top"
                :enterable="false">
                <el-button type="warning" icon="el-icon-setting" circle size="mini" @click="setRole(scope.row)"></el-button>
              </el-tooltip>
              <el-button type="danger" icon="el-icon-delete" circle size="mini" @click="removeUserById(scope.row.id)"></el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 修改用户权限的对话框 -->
      <!-- 对话框关闭事件的处理函数和对话框中取消按钮的监听函数指向一个函数，都会重置下拉菜单的选项 -->
      <el-dialog
        title="修改用户角色"
        :visible.sync="editRightsDIalog"
        width="50%"
        @close="cancelRoleChange(recentRole.id)">
        <!-- 使用单向绑定设置el-col的属性span与offset防止控制台报错说需要整型数却得到字符型 -->
        <el-row>
          <el-col :span="4" :offset="1">
            <span>用户名称：</span>
          </el-col>
          <el-col :span="6">
            <span>{{recentRole.username}}</span>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="4" :offset="1">
            <span>用户角色：</span>
          </el-col>
          <el-col :span="6">
            <span>{{recentRole.role_name}}</span>
          </el-col>
        </el-row>
        <el-row></el-row>
        <el-row>
          <el-col :span="4" :offset="1">
            <span>分配角色：</span>
          </el-col>
          <el-col :span="6">
            <!-- 角色列表选择器 -->
            <!-- 本组件el-select中的v-model绑定了el-option中被选中的选项单向绑定的value值，
              el-option中key属性作为唯一索引，label属性是下拉框中展示的选项名，value属性如上所述，是传出给el-select组件的属性，
              后台接口要求变更角色请求时需要携带用户id和角色id两个参数，
              所以v-model绑定的是选中的角色id，用户id在对话框确定按钮的点击事件中获取 @change="RoleselectionChange(RoleSelected)"-->
            <el-select v-model="RoleSelected" placeholder="请选择">
              <el-option
                v-for="item in roleList"
                :key="item.id"
                :label="item.roleName"
                :value="item.id"
                ref="RoleOptionRef">
              </el-option>
            </el-select>
          </el-col>
        </el-row>
        <span slot="footer" class="dialog-footer">
          <el-button @click="cancelRoleChange(recentRole.id)">取 消</el-button>
          <el-button type="primary" @click="commitRoleChange(recentRole.id)">确 定</el-button>
        </span>
      </el-dialog>

      <!-- 分页盒子 -->
      <!-- pageination组件中绑定了两个事件，size-change监听每页数据条数的变化，
        current-change监听当前页码的变化。
        curent-page：当前页面；
        page-sizes作用：选择当前页面显示几条数据；
        page-size：每页多少条数据；
        total：总数据 -->
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="queryInfo.page"
        :page-sizes="[3, 4, 5]"
        :page-size="queryInfo.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total">
      </el-pagination>
    </el-card>

    <!-- 修改用户窗口区域 -->
    <el-dialog
      title="修改用户信息"
      :visible.sync="editUserDialog"
      width="50%"
      @close="editUserDialogClosed">

      <!-- 有验证的表单区域 -->
      <el-form :model="editUserForm" :rules="editUserRules" ref="editUserRef" label-width="70px">
        <el-form-item label="用户名">
          <!-- disabled禁用用户名输入框 -->
          <el-input v-model="editUserForm.username" disabled></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="editUserForm.email"></el-input>
        </el-form-item>
        <el-form-item label="手机号" prop="mobile">
          <el-input v-model="editUserForm.mobile"></el-input>
        </el-form-item>
      </el-form>

      <!-- 修改用户窗口的关闭区域 -->
      <span slot="footer" class="dialog-footer">
        <el-button @click="editUserDialog = false">取 消</el-button>
        <el-button type="primary" @click="submitEdit">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { getUserList } from '@/api/userListApi.js'
import { changeUserStatus } from '@/api/changeUserStateApi.js'
import { addUser } from '@/api/addUserApi.js'
import { getUserById } from '@/api/selectUserByIdApi.js'
import { putUserInfo } from '@/api/editUserInfoApi.js'
import { deleteUser } from '@/api/deleteUserApi.js'
import { getRolesList } from '@/api/getRolesApi.js'
import { putUserRole } from '@/api/changeUserRoleApi.js'
export default {
  name: 'myUser',
  data () {
    // 邮箱校验函数
    const checkEmail = (rule, value, cb) => {
      // 邮箱验证正则：
      // "数字/大小字母" @ "数字/大小字母" .(要转译) "数字/大小字母"
      const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
      if (regEmail.test(value)) return cb()
      cb(new Error('请输入合法的邮箱'))
    }
    // 手机号验证函数
    const checkPhone = (rule, value, cb) => {
      // 手机号验证正则：
      // "地区前缀：0/ 86/ 17951中的数字" +
      // "运营商前缀：15x/ 17x/ 18x/ 14五或七" +
      // "八位0到9的数字"
      const regPhone = /^(0|86|17951)?(13[0-9]|15[0-9]|17[678]|18[0-9]|14[57])[0-9]{8}$/
      if (regPhone.test(value)) return cb()
      cb(new Error('请输入合法的手机号'))
    }
    // 手机号校验函数
    return {
      queryInfo: {
        query: '',
        page: 1,
        size: 3
      },
      userList: [],
      total: 0,
      addUserDialog: false,
      editUserDialog: false,
      // 添加用户窗口的表单数据对象
      addUserForm: {
        username: '',
        password: '',
        email: '',
        mobile: ''
      },
      // 修改用户窗口的表单数据
      editUserForm: {
        id: 0,
        username: '',
        email: '',
        mobile: ''
      },
      // 添加用户窗口的表单验证对象
      addUserRules: {
        username: [
          {
            required: true,
            message: '用户名不能为空',
            trigger: 'blur'
          },
          {
            min: 3,
            max: 9,
            message: '用户名长度在3-9个字符间',
            trigger: 'blur'
          }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 15, message: '密码长度在6-15个字符间', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' },
          // 自定义校验规则
          { validator: checkEmail, trigger: 'blur' }
        ],
        mobile: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          // 自定义校验规则
          { validator: checkPhone, trigger: 'blur' }
        ]
      },
      // 修改用户窗口的校验规则
      editUserRules: {
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 15, message: '密码长度在6-15个字符间', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' },
          // 自定义校验规则
          { validator: checkEmail, trigger: 'blur' }
        ],
        mobile: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          // 自定义校验规则
          { validator: checkPhone, trigger: 'blur' }
        ]
      },
      // 修改用户权限对话框的显隐控制变量
      editRightsDIalog: false,
      // 修改用户角色对话框中的当前角色信息
      recentRole: {},
      // 请求到的角色列表信息
      roleList: [],
      // 存放选择框里面已选中的角色id的变量
      RoleSelected: ''
    }
  },
  created () {
    this.initUserList()
  },
  methods: {
    // 请求用户列表函数
    async initUserList () {
      const { data: res } = await getUserList(this.queryInfo.query, this.queryInfo.page, this.queryInfo.size)
      if (res.meta.status !== 200) {
        return this.$notify.error({
          title: '用户列表获取失败',
          message: res.meta.msg
        })
      }
      // this.userList = [...this.userList, ...res.data.users]
      this.userList = res.data.users
      this.total = res.data.total
    },
    // 更新用户状态函数
    async changeUserState (userInfo) {
      const { data: res } = await changeUserStatus(userInfo.id, userInfo.mg_state)
      if (res.meta.status !== 200) {
        return this.$notify.error({
          title: '更新用户状态失败',
          message: res.meta.msg
        })
      }
      return this.$notify.success({
        title: '更新用户状态成功'
      })
    },
    // 发送新增用户信息
    async postUserInfo () {
      // 将整个用户信息对象传给api文件夹中的addUserApi.js中的函数
      const { data: res } = await addUser(this.addUserForm)
      // 返回值状态为201表示新增成功，如果不是201则提示错误
      if (res.meta.status !== 201) {
        return this.$notify.error({
          title: '新增用户失败',
          message: res.meta.msg
        })
      }
      // 上面If没有return出去表示状态为201，新增成功，发出提示，同时重新获取用户列表
      this.initUserList()
      this.$notify.success({ title: '新增用户成功' })
    },
    // 调用接口提交修改的数据
    async editUserInfo () {
      // 使用api中封装的putUserInfo方法提交修改后的数据
      const { data: res } = await putUserInfo(
        this.editUserForm.id,
        this.editUserForm.email,
        this.editUserForm.mobile
      )
      // 检查返回对象的状态，非200修改失败
      if (res.meta.status !== 200) {
        return this.$notify.error({ title: '修改数据失败', message: res.meta.msg })
      }
      // if没有返回出去说明请求成功，刷新用户列表。
      this.initUserList()
      this.$notify.success({ title: '修改成功' })
    },
    // 显示修改窗口的函数，其中需要根据id从后台获取用户信息
    async showEditUserDialog (id) {
      const { data: res } = await getUserById(id)
      // 返回对象的状态不是200说明请求失败，提示用户
      if (res.meta.status !== 200) {
        return this.$notify.error({
          title: '获取用户信息失败',
          message: res.meta.msg
        })
      }
      // 没有return出去说明请求成功，将数据存入edituserform表单
      this.editUserForm = res.data
      // 表单中有数据了再将控制窗口隐现的属性visible绑定的变量editUserDialog改为true
      this.editUserDialog = true
    },
    // 确认弹窗函数
    async removeUserById (id) {
      // 注释代码为element-ui提供的示例
      // 由于messagebox默认调用的提示框是message，而之前我使用的提示框都是notify
      // 为了统一提示弹窗类型，需要对$confirm进行修改，
      // 可以看出$confirm返回了promise对象，故使用async-await修饰
      // 通过if判断返回值状态来调用不同的$notify函数
      // this.$confirm('此操作将永久删除该条用户数据, 是否继续?', '注意', {
      //   confirmButtonText: '确定',
      //   cancelButtonText: '取消',
      //   type: 'warning'
      // }).then(() => {
      //   this.$message({
      //     type: 'success',
      //     message: '删除成功!'
      //   })
      // }).catch(() => {
      //   this.$message({
      //     type: 'info',
      //     message: '已取消删除'
      //   })
      // })
      // await修饰后，发现点确定返回confirm，点取消会报错，
      // 所以使用catch获取错误，并返回该错误，这样作为判断的两个状态就有了，
      // catch中输出发现err值为cancel，故返回err，箭头函数只有一句话且是{return xxx}时，
      // 可以省略return和大括号，简写为xxx
      const result = await this.$confirm('此操作将永久删除该条用户数据, 是否继续?', '注意', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).catch(err => err)
      // 此时可以使用$notify
      if (result === 'cancel') {
        return this.$notify.info({ title: '已取消删除操作' })
      }
      const { data: res } = await deleteUser(id)
      if (res.meta.status !== 200) {
        return this.$notify.error({ title: '删除失败', message: res.meta.msg })
      }
      this.$notify.success({ title: '删除成功' })
      this.initUserList()
    },

    // 监听数据条数的事件函数
    handleSizeChange (newSize) {
      // v-bind是单向绑定，所以需要手动修改vue实例中相应的数据
      this.queryInfo.size = newSize
      // 修改数据后使用定义好的函数来请求后端数据，这时initUserList函数调用getUserList函数时传入的参数就是最新的参数
      this.initUserList()
    },
    // 监听当前页码的事件函数
    handleCurrentChange (nowPage) {
      this.queryInfo.page = nowPage
      this.initUserList()
    },

    // 监听“添加用户窗口”关闭的事件处理函数，用来重置表单内容
    addUserDialogClosed () {
      // dialog组件有ref(引用)属性，可以在vue实例的$refs属性中找到，
      // dialog组件自身有一个resetFields方法用来重置表单
      this.$refs.addUserRef.resetFields()
    },

    // 监听“修改用户信息窗口”关闭事件的函数，用来清除校验提示内容
    editUserDialogClosed () {
      // dialog表单组件的ref属性将它的值给了vue实例的$refs属性
      // 通过$refs找到修改窗口调用clearvalidate清除校验提示
      this.$refs.editUserRef.resetFields()
    },

    // 监听“添加用户窗口”确定按钮的事件，用来提交表单中的用户数据到数据库
    addUser () {
      this.$refs.addUserRef.validate(valid => {
        // 如果表单校验不通过，发出提示
        if (!valid) {
          return this.$notify.error({
            titile: '输入数据格式错误'
          })
        }
        // 校验通过就调接口新增用户
        this.postUserInfo()
      })
      this.addUserDialog = false
    },
    // 修改用户信息提交
    submitEdit () {
      this.$refs.editUserRef.validate(valid => {
        if (!valid) {
          return this.$notify.error({ title: '表单校验未通过' })
        }
        this.editUserInfo()
        this.editUserDialog = false
      })
    },
    // 修改用户角色按钮的事件监听函数
    async setRole (row) {
      // 为了将角色信息渲染到对话框中，需要先将当前角色信息绑定到vue实例上
      this.recentRole = row
      // 获取角色列表
      const { data: res } = await getRolesList()
      if (res.meta.status !== 200) {
        return this.$notify({ title: '角色列表获取失败', message: res.meta.msg })
      }
      // 没有return出去则角色列表获取成功，存入vue实例中的roleList数组
      this.roleList = res.data
      this.editRightsDIalog = true
    },
    // 对话框关闭事件监听函数（包括取消按钮和右上角点叉叉关闭和点空白处关闭）
    async cancelRoleChange (uid) {
      // 将选项菜单的值恢复初始状态
      // 点击事件触发时会传入用户的id值和权限名称
      const { data: res } = await getUserById(uid)
      if (res.meta.status !== 200) {
        return this.$notify.error({ title: '下拉菜单重置失败', message: res.meta.msg })
      }
      // 没有返回表示请求成功，重置下拉菜单的值为当前用户的角色id该id存放在vue实例身上的RoleSelected变量中
      this.RoleSelected = res.data.role_id
      // 关闭对话框
      this.editRightsDIalog = false
    },
    // 修改角色对话框确定事件
    async commitRoleChange (uid) {
      // 补齐请求参数rid，被选中的角色id存放在vue实例中的RoleSelected变量中
      // 调用接口将新的权限存入后台
      const { data: res } = await putUserRole(uid, this.RoleSelected)
      if (res.meta.status !== 200) {
        return this.$notify.error({ title: '修改用户角色失败', message: res.meta.msg })
      }
      // 没有返回出去说明修改成功，提示用户
      this.$notify.success({ title: '角色修改成功' })
      // 需要将新的用户权限列表渲染到页面中
      this.initUserList()
      // 关闭对话框
      this.editRightsDIalog = false
    }
  }
}
</script>

<style lang="less" scoped>
  .el-table {
    margin-top: 20px;
  }
  .box-card {
    .el-button:active {
      background-color: #e3e3e3;
    }
  }
  // 为修改用户表单中的el-row组件设置外边距和内部元素垂直居中
  .el-dialog__body > .el-row {
    display: flex;
    align-items: center;
    margin: 10px;
  }
</style>
